<?php

namespace app\service;


use app\exception\ParameterException;
use Couchbase\PreparedStatementException;
use Psr\SimpleCache\InvalidArgumentException;
use think\facade\Config;
use think\Response;
use think\captcha\Captcha;
use think\facade\Cache;
class CaptchaService
{

    //缓存前缀
    const CACHE_PREFIX = 'captcha_';

    private $cache;

    private $im = null; // 验证码图片实例
    private $color = null; // 验证码字体颜色
    protected $codeSet = '23456789ABCDEFGHJKLMNPQRSTUVWXYZ';
    // 默认配置
    protected $config = [
        'length'      => 4,      // 验证码位数
        'fontSize'    => 25,     // 字体大小(px)
        'imageH'      => 0,      // 验证码高度
        'imageW'      => 0,      // 验证码宽度
        'useCurve'    => true,   // 是否画混淆曲线
        'useNoise'    => false,  // 是否添加杂点(已禁用)
        'bg'          => [243, 251, 254], // 背景颜色
        'fontttf'     => '',     // 字体文件路径
        'useZh'       => false,  // 使用中文验证码
        'math'        => false,  // 算术验证码
        'alpha'       => 0,      // 透明度(0-127)
        'api'         => false,  // API模式
        'fontPath'    => '',     // 字体文件目录
        'bgPath'      => '',     // 背景图片目录
        'expire'      => 1800,   // 验证码过期时间(s)
    ];

    // 简化后的验证码字符集合(仅数字和大写字母)


    /**
     * 构造函数
     * @param array $config 配置参数
     */
    public function __construct(array $config = [])
    {
        $this->cache = Cache::store('file');
        // 合并配置参数
        $this->config = array_merge($this->config, Config::get('captcha', []), $config);

        // 设置字体路径
        if (empty($this->config['fontPath'])) {
            $this->config['fontPath'] = __DIR__ . '/ttfs/';
        }
    }

    /**
     * 生成验证码
     * @param string $uniqueId 前端传递的唯一标识(如时间戳)
     * @return string 图片二进制内容
     */
    public function create(string $uniqueId = ''): string
    {
        // 清理过期缓存
        $this->clearExpiredCaptchas();

        // 如果未提供 uniqueId，则生成一个默认值
        if (empty($uniqueId)) {
            $uniqueId = uniqid('captcha_');
        }

        // 生成验证码文本
        $generator = $this->generate($uniqueId);

        // 计算图片宽高
        $this->config['imageW'] = $this->config['imageW'] ?: $this->config['length'] * $this->config['fontSize'] * 1.5;
        $this->config['imageH'] = $this->config['imageH'] ?: $this->config['fontSize'] * 2;

        // 创建图片资源
        $this->im = imagecreate((int)$this->config['imageW'], (int)$this->config['imageH']);

        // 设置背景色
        $bgColor = imagecolorallocate(
            $this->im,
            $this->config['bg'][0],
            $this->config['bg'][1],
            $this->config['bg'][2]
        );
        imagefill($this->im, 0, 0, $bgColor);

        // 设置字体颜色
        $this->color = imagecolorallocate($this->im, mt_rand(0, 100), mt_rand(0, 100), mt_rand(0, 100));

        // 添加干扰线
        if ($this->config['useCurve']) {
            $this->writeCurve();
        }

        // 绘制验证码
        $text = str_split($generator['value']);
        $space = $this->config['imageW'] / $this->config['length'];

        foreach ($text as $index => $char) {
            // 计算位置
            $x = $space * $index + mt_rand(5, 10);
            $y = $this->config['imageH'] / 2 + $this->config['fontSize'] / 2;
            $angle = mt_rand(-15, 15);

            imagettftext(
                $this->im,
                (int)$this->config['fontSize'],
                $angle,
                (int)$x,
                (int)$y,
                $this->color,
                $this->getFont(),
                $char
            );
        }

        ob_start();
        imagepng($this->im);
        $content = ob_get_clean();
        imagedestroy($this->im);

        return $content;
    }

    /**
     * 验证验证码
     * @param string $code 用户输入的验证码
     * @param string $uniqueId 前端传递的唯一标识(如时间戳)
     * @return bool
     */
    public function check(string $code, string $uniqueId = ''): bool
    {
        if (empty($uniqueId)) {
            return false;
        }

        // 从 Cache 中获取数据
        $cacheData = $this->cache->get($uniqueId);

        if (!$cacheData || time() - $cacheData['time'] > $this->config['expire']) {
            $this->removeCaptchaFromRecords($uniqueId);
            return false;
        }

        // 验证码校验
        $result = password_verify(strtoupper($code), $cacheData['key']);

        return $result;
    }

    /**
     * 生成验证码文本
     * @param string $uniqueId 前端传递的唯一标识(如时间戳)
     * @return array ['value' => 显示的文本, 'key' => 加密后的验证码]
     */
    protected function generate(string $uniqueId): array
    {

        // 将验证码信息存储到 Cache 中
        try {
            $bag = '';
            $characters = str_split($this->codeSet);

            for ($i = 0; $i < $this->config['length']; $i++) {
                $bag .= $characters[random_int(0, count($characters) - 1)];
            }

            $key = strtoupper($bag);

            // 使用 Bcrypt 加密验证码
            $hash = password_hash($key, PASSWORD_BCRYPT, ['cost' => 10]);
            $this->cache->set($uniqueId, [
                'key' => $hash,
                'time' => time(),
                // 'raw' => $key // 调试用，正式环境移除
            ], $this->config['expire']);

            // 记录到清理队列
            $this->addCaptchaToRecords($uniqueId);

            return ['value' => $bag, 'key' => $hash];
        } catch (InvalidArgumentException $e) {
            throw new ParameterException($e->getMessage());
        }

    }

    /**
     * 添加验证码记录到清理队列
     */
    protected function addCaptchaToRecords(string $uniqueId): void
    {
        $records = $this->cache->get('captcha_records', []);
        $records[$uniqueId] = time() + $this->config['expire'];

        // 限制最大记录数，防止内存占用过大
        if (count($records) > 1000) {
            $records = array_slice($records, -500, null, true);
        }

        $this->cache->set('captcha_records', $records);
    }

    /**
     * 从清理队列中移除验证码记录
     */
    protected function removeCaptchaFromRecords(string $uniqueId): void
    {
        $records = $this->cache->get('captcha_records', []);
        unset($records[$uniqueId]);
        $this->cache->set('captcha_records', $records);
    }

    /**
     * 清理过期的验证码缓存
     */
    protected function clearExpiredCaptchas(): void
    {
        // 每小时清理一次
        $lastClear = Cache::get('last_captcha_clear', 0);
        if (time() - $lastClear < 3600) {
            return;
        }

        $records = $this->cache->get('captcha_records', []);
        $now = time();
        $cleaned = false;

        foreach ($records as $uid => $expireTime) {
            if ($expireTime < $now) {
                $this->cache->delete($uid);
                unset($records[$uid]);
                $cleaned = true;
            }
        }

        if ($cleaned) {
            $this->cache->set('captcha_records', $records);
            $this->cache->set('last_captcha_clear', time());
        }
    }

    /**
     * 获取字体文件路径
     * @return string
     * @throws Exception
     */
    protected function getFont(): string
    {
        if (!empty($this->config['fontttf'])) {
            return $this->config['fontttf'];
        }

        $fonts = glob($this->config['fontPath'] . '*.ttf') +
            glob($this->config['fontPath'] . '*.otf');

        if (empty($fonts)) {
            throw new PreparedStatementException('验证码字体文件不存在，请检查字体路径: ' . $this->config['fontPath']);
        }

        return $fonts[array_rand($fonts)];
    }

    /**
     * 画干扰曲线
     */
    protected function writeCurve(): void
    {
        $px = $py = 0;

        // 曲线前部分
        $A = mt_rand(1, (int)($this->config['imageH'] / 2)); // 振幅
        $b = mt_rand((int)(-$this->config['imageH'] / 4), (int)($this->config['imageH'] / 4)); // Y轴偏移
        $f = mt_rand((int)(-$this->config['imageH'] / 4), (int)($this->config['imageH'] / 4)); // X轴偏移
        $T = mt_rand($this->config['imageH'], $this->config['imageW'] * 2); // 周期
        $w = (2 * M_PI) / $T;

        $px1 = 0; // 起始X坐标
        $px2 = mt_rand((int)($this->config['imageW'] / 2), (int)($this->config['imageW'] * 0.8)); // 结束X坐标

        for ($px = $px1; $px <= $px2; $px++) {
            if ($w != 0) {
                $py = $A * sin($w * $px + $f) + $b + $this->config['imageH'] / 2;
                $i = (int)($this->config['fontSize'] / 5);
                while ($i > 0) {
                    imagesetpixel($this->im, $px + $i, $py + $i, $this->color);
                    $i--;
                }
            }
        }

        // 曲线后部分
        $A = mt_rand(1, (int)($this->config['imageH'] / 2));
        $f = mt_rand((int)(-$this->config['imageH'] / 4), (int)($this->config['imageH'] / 4));
        $T = mt_rand($this->config['imageH'], $this->config['imageW'] * 2);
        $w = (2 * M_PI) / $T;
        $b = $py - $A * sin($w * $px + $f) - $this->config['imageH'] / 2;
        $px1 = $px2;
        $px2 = $this->config['imageW'];

        for ($px = $px1; $px <= $px2; $px++) {
            if ($w != 0) {
                $py = $A * sin($w * $px + $f) + $b + $this->config['imageH'] / 2;
                $i = (int)($this->config['fontSize'] / 5);
                while ($i > 0) {
                    imagesetpixel($this->im, $px + $i, $py + $i, $this->color);
                    $i--;
                }
            }
        }
    }
}
